#include "tiles.h"
//#define DEBUG

void r8b_freeSurfaceTiles(R8B_Surface_Tiles *s) {
  free(s->rects);
  free(s);
}

static void print_R8B_Surface_Tiles(R8B_Surface_Tiles *ptr) {
  if(ptr == NULL) {
    puts("NULL");
  } else {
    printf("R8B_Surface_Tiles@%p = {\n", ptr);
    printf("\t.*original=%p\n", ptr->original);
    printf("\t.num_tiles=$%X\n", ptr->num_tiles);
    printf("\t.width=$%X\n", ptr->width);
    printf("\t.height=$%X\n", ptr->height);
    printf("\t.*rects=%p\n", ptr->rects);
    puts("}");
  } 
}

R8B_Surface_Tiles *r8b_createSurfaceTiles(SDL_Surface *src, int w, int h) {
  R8B_Surface_Tiles *ptr;
  SDL_Rect *rptr;
  int x, y;
  int tiles_in_x = src->w / w;
  int tiles_in_y = src->h / h;
  int num_tiles = (long)tiles_in_x * tiles_in_y;

  ptr = malloc(sizeof(R8B_Surface_Tiles));
  rptr = malloc(sizeof(SDL_Rect) * num_tiles);
  if(ptr == NULL || rptr == NULL) {
    free(ptr);
    free(rptr);
    return NULL;
  }
  ptr->original = src;
  ptr->num_tiles = num_tiles;
  ptr->width = w;
  ptr->height = h;
  ptr->rects = rptr;
  for(y = 0; y < tiles_in_y; ++y) {
    for(x = 0; x < tiles_in_x; ++x) {
      //printf("rptr=%p\n", rptr);
      rptr->x = x * w;
      rptr->y = y * h;
      rptr->w = w;
      rptr->h = h;
      rptr++;
    }
  }
  return ptr;
}

static void print_SDL_Rect(const SDL_Rect *ptr) {
  if(ptr == NULL) {
    printf("print_SDL_Rect@%p=NULL\n", &print_SDL_Rect);
  } else {
    printf("SDL_Rect@%p = {\n", ptr);
    printf("\t.x=$%X\n", (int)ptr->x);
    printf("\t.y=$%X\n", (int)ptr->y);
    printf("\t.w=$%X\n", (int)ptr->w);
    printf("\t.h=$%X\n", (int)ptr->h);
    puts("}");
  } 
}


int r8b_blitTile(R8B_Surface_Tiles *st, int num, SDL_Surface *dst, SDL_Rect *dstrect) {
#ifdef DEBUG
  printf("int r8b_blitTile@%p(R8B_Surface_Tiles *st=%p, int num=$%lX, SDL_Surface *dst=$%p, SDL_Rect *dstrect=$%p)\n", &r8b_blitTile, st, num, dst, dstrect);
#endif
  if(num < 0 || num >= st->num_tiles) return -1;
#ifdef DEBUG
  print_R8B_Surface_Tiles(st);
  print_SDL_Rect(dstrect);
  printf("\tst->rects+num=%p %p\n", st->rects + num, &((st->rects)[num]));
  print_SDL_Rect(st->rects + num);
#endif
  return SDL_BlitSurface(st->original, st->rects + num, dst, dstrect);
}


int r8b_blitTileXY(R8B_Surface_Tiles *st, int num, SDL_Surface *dst, int x, int y) {
  SDL_Rect r = { x, y, st->width, st->height};
  return r8b_blitTile(st, num, dst, &r);
}


/*! \brief allocate and partially initialise R8B_Tiled_Screen
 *
 * \warning Not all fields are initilised!
 * 
 * Currently only width, height, cursor position, default fill are
 * initialised.
 *
 * \param width tiles in x
 * \param height tiles in y
 * \param default_fill character to fill with
 * \return pointer to allocated structure or NULL
 */
static R8B_Tiled_Screen *_r8b_createTiledScreen__alloc_tiled_screen_struct(
									   int width,
									   int height,
									   int default_fill
									   ) {
  R8B_Tiled_Screen *ptr = calloc(1, sizeof(R8B_Tiled_Screen));
  
  if(ptr == NULL) return NULL;

  ptr->ticks_on_creation = SDL_GetTicks();
  ptr->width = width;
  ptr->height = height;
  // Done by calloc
  /* ptr->crsr_x = 0; */
  /* ptr->crsr_y = 0; */
  ptr->default_fill = default_fill;
  return ptr;
}


R8B_Tiled_Screen *r8b_createTiledScreen_using_surface(int width, int height, int default_fill, SDL_Surface *surf, int tile_width, int tile_height) {
  R8B_Surface_Tiles *tiles = r8b_createSurfaceTiles(surf, tile_width, tile_height);
  R8B_Tiled_Screen *ptr = _r8b_createTiledScreen__alloc_tiled_screen_struct(width, height, default_fill);
  int *array_ptr = malloc(sizeof(int) * width * height);

  if((ptr == NULL) || (tiles == NULL) || (array_ptr == NULL)) {
    free(ptr);
    r8b_freeSurfaceTiles(tiles);
    free(array_ptr);
    return NULL;
  }
  ptr->array = array_ptr;
  ptr->tiles = tiles;
  ptr->array_owned = 1;
  ptr->tiles_owned = 1;
#ifdef DEBUG
  printf("R8B_Tiled_Screen *=$%p r8b_createTiledScreen_using_surface(int width=$%x, int height=$%x, int default_fill=$%x, SDL_Surface *surf=$%p, int tile_width=$%x, int tile_height=$%x)\n", ptr, width, height, default_fill, surf, tile_width, tile_height);
#endif
  r8b_clearScreen(ptr);
  return ptr;
}

R8B_Tiled_Screen *r8b_createTiledScreen(int width, int height, int default_fill, R8B_Surface_Tiles *tiles) {
  int num = (long)width * (long)height;
  int *array_ptr = malloc(sizeof(int) * num);
  R8B_Tiled_Screen *ptr = _r8b_createTiledScreen__alloc_tiled_screen_struct(width, height, default_fill);

  if(ptr == NULL || array_ptr == NULL) {
    free(ptr);
    free(array_ptr);
    return NULL;
  }
  ptr->array = array_ptr;
  ptr->tiles = tiles;
  ptr->array_owned = 1;
  ptr->tiles_owned = 0;
  r8b_clearScreen(ptr);
  return ptr;
}


R8B_Tiled_Screen *r8b_createTiledScreen_with_array(int width, int height, int default_fill, R8B_Surface_Tiles *tiles, int *extern_array) {
  R8B_Tiled_Screen *ptr = _r8b_createTiledScreen__alloc_tiled_screen_struct(width, height, default_fill);

  if(ptr == NULL) return NULL;

  ptr->array = extern_array;
  ptr->tiles = tiles;
  ptr->array_owned = 0;
  ptr->tiles_owned = 0;
  return ptr;
}


void r8b_freeTiledScreen(R8B_Tiled_Screen *screen) {
  if(screen == NULL) return;
#ifdef DEBUG
  printf("void r8b_freeTiledScreen(R8B_Tiled_Screen *screen=$%p)\n", screen);
#endif
  if(screen->array_owned == 1) free(screen->array);
  if(screen->tiles_owned == 1) r8b_freeSurfaceTiles(screen->tiles);
  if(screen->draw_cursor_fptr != NULL) {
    if(screen->draw_cursor_fptr->onFree)
      screen->draw_cursor_fptr->onFree(screen);
    free(screen->draw_cursor_fptr);
  }
  free(screen);
}


int r8b_blitScreenXY(R8B_Tiled_Screen *src, SDL_Surface *dst, int x, int y) {
  SDL_Rect r = { x, y, 0, 0 };
  return r8b_blitScreen(src, dst, &r);
}

static int draw_cursor(R8B_Tiled_Screen *src, SDL_Surface *dst, SDL_Rect *dstrect) {
  short freq = src->draw_cursor_fptr->data[1].s;

  if(freq == 0 || (((SDL_GetTicks() - src->ticks_on_creation) / freq) & 1) == 1) {
    int w = src->tiles->width;
    int h = src->tiles->height;
    int x = src->crsr_x;
    int y = src->crsr_y;
    SDL_Rect r = { dstrect->x+x*w, dstrect->y+y*h, w, h };

    return SDL_FillRect(dst, &r, (src->draw_cursor_fptr->data)[0].U32);
  }
  return 0;
}

int r8b_addCursorTiledScreen(R8B_Tiled_Screen *screen, Uint32 colour, short freq) {
  R8B_Tiled_Screen_Additional_Functionality *ptr = calloc(1, sizeof(R8B_Tiled_Screen_Additional_Functionality));

  if(ptr == NULL) return -1;
  ptr->onBlit = &draw_cursor;
  ptr->data[0].U32 = colour;
  ptr->data[1].s = freq;
  screen->draw_cursor_fptr = ptr;
  return 0;
}

int r8b_blitScreen(R8B_Tiled_Screen *src, SDL_Surface *dst, SDL_Rect *dstrect) {
#ifdef DEBUG
  printf("int r8b_blitScreen(R8B_Tiled_Screen *src=%p, SDL_Surface *dst=%p, SDL_Rect *dstrect=%p)\n", src, dst, dstrect);
#endif
  int x, y;
  SDL_Rect r;
  int ret = 0;
  int tile_width = src->tiles->width;
  int tile_height = src->tiles->height;

  for(y = 0; y < src->height; ++y) {
    for(x = 0; x < src->width; ++x) {
      int pos = y * src->width + x;
      int w = dstrect->w != 0 ? dstrect->w - x * tile_width : 0;
      int h = dstrect->h != 0 ? dstrect->h - y * tile_height : 0;

      if(w >= 0 && h >= 0) {
        r.x = dstrect->x + x * tile_width;
	r.y = dstrect->y + y * tile_height;
        //r.x = 0 + x * tile_width;
	//r.y = 0 + y * tile_height;
	r.w = w;
	r.h = h;

	if(r8b_blitTile(src->tiles, src->array[pos], dst, &r) != 0) {
	  ret = -1;
	}
      }
    }
  }
  if(src->draw_cursor_fptr != NULL) {
    if(src->draw_cursor_fptr->onBlit(src, dst, dstrect) < 0) ret = -1;
  }
  return ret;
}


int r8b_blitScreenPart(R8B_Tiled_Screen *src, SDL_Rect *scrprt, SDL_Surface *dst, SDL_Rect *dstrect) {
  int x, y;
  int tile;
  SDL_Rect sr;
  SDL_Rect dr;
  int tile_width = src->tiles->width;
  int tile_height = src->tiles->height;
  int ret = 0;
  int x0 = scrprt->x;
  int y0 = scrprt->y;
  Uint16 w0 = scrprt->w;
  Uint16 h0 = scrprt->h;
  int dest_x = dstrect->x;
  int dest_y = dstrect->y;

  /* printf("int r8b_blitScreenPart(R8B_Tiled_Screen *src=%p, SDL_Rect *scrprt=%p, SDL_Surface *dst=%p, SDL_Rect *dstrect=%p\n", src, scrprt, dst, dstrect); */
  /* print_SDL_Rect(scrprt); */
  dr.w = 0;
  dr.h = 0;
  for(y = y0; y < y0 + h0; ) {
    int y_ = y / tile_height;
    int y_y_ = y - y_ * tile_height;
    for(x = x0; x < x0 + w0; ) {
      int x_ = x / tile_width;
      int x_x_ = x - x_ * tile_width;
      if((tile = r8b_peekScreen(src, x_, y_)) >= 0) {
	//printf("x=$%X y=$%X x_=$%X y_=$%X tile=$%X\n", x, y, x_, y_, tile);
	sr = src->tiles->rects[tile];
	if(x == x0) {
	  sr.x += x0 - x_ * tile_width;
	  sr.w = tile_width - (x_x_);
	} else if(x + tile_width > x0 + w0) {
	  sr.w = x0 + w0 - x;
	} else {
	  sr.w = tile_width;
	}
	if(y == y0) {
	  sr.h = tile_height - (y_y_);
	  sr.y += y0 - y_ * tile_height;
	} else if(y + tile_height > y0 + h0) {
	  sr.h = y0 + h0 - y;
	} else {
	  sr.h = tile_height;
	}
	dr.x = x - x0 + dest_x;
	dr.y = y - y0 + dest_y;
	/* //r8b_blitTile(src->tiles, src->array[pos], dst, &r) */
	/* print_SDL_Rect(&sr); */
	/* print_SDL_Rect(&dr); */
	ret += SDL_BlitSurface(src->tiles->original, &sr, dst, &dr);
      }
      x += x == x0 ? tile_width - (x_x_) : tile_width;
    }
    y += y == y0? tile_height - (y_y_): tile_height;
  }
  return ret;
}


int r8b_peekScreen(R8B_Tiled_Screen *dst, int x, int y) {
  if(x >= 0 && y >= 0 && x < dst->width && y < dst->height) {
    return dst->array[y * dst->width + x];
  }
  return -1;
}


void r8b_pokeScreen(R8B_Tiled_Screen *dst, int x, int y, int c) {
#ifdef DEBUG
  //printf("void r8b_pokeScreen(R8B_Tiled_Screen *dst=$%p, int x=$%x, int y=$%x, int c=$%x)\n", dst, x, y, c);
  //printf("\tdst->width=$%x, dst->height=$%x\n", dst->width, dst->height);
#endif
  if(x >= 0 && y >= 0 && x < dst->width && y < dst->height) {
#ifdef DEBUG
    //printf("\t(x >= 0 && y >= 0 && x < dst->width && y < dst->height) == true\n");
#endif
    dst->array[y * dst->width + x] = c;
  }
}


void r8b_fillScreen(R8B_Tiled_Screen *dst, int c) {
  int x, y;
  for(y = 0; y < dst->height; ++y) {
    for(x = 0; x < dst->width; ++x) {
      r8b_pokeScreen(dst, x, y, c);
    }
  }
}


void r8b_clearScreen(R8B_Tiled_Screen *dst) {
  r8b_fillScreen(dst, dst->default_fill);
}

void r8b_scrollScreenUp(R8B_Tiled_Screen *scr) {
  int len = scr->width;
  int *src = scr->array;
  int *dst = scr->array + scr->width;
  register int fill = scr->default_fill;

  len *= scr->height;
  while(dst < scr->array + len) {
    *src++ = *dst++;
  }
  for(dst = scr->array + len - scr->width; dst < scr->array + len; ++dst) {
    *dst = fill;
  }
}

void r8b_moveScreenCursor(R8B_Tiled_Screen *scr, int dx, int dy) {
  int x = scr->crsr_x + dx;
  int y = scr->crsr_y + dy;
  int w = scr->width;
  int h = scr->height;

  while(x >= w) {
    x -= w;
    ++y;
  }
  while(y >= h) {
    r8b_scrollScreenUp(scr);
    --y;
  }
  if(x < 0) x = 0;
  if(y < 0) y = 0;
  scr->crsr_x = x;
  scr->crsr_y = y;
}

void r8b_putCharScreen(R8B_Tiled_Screen *scr, int c) {
  r8b_pokeScreen(scr, scr->crsr_x, scr->crsr_y, c);
  r8b_moveScreenCursor(scr, 1, 0);
}

void r8b_printScreen(R8B_Tiled_Screen *scr, int *tilenums) {
  int *p;
  
  for(p = tilenums; *p != -1; ++p) {
    r8b_putCharScreen(scr, *p);
  }
}

int r8b_defaultCharConversionFun(char c) {
  return (unsigned char)c;
}

int r8b_c64CharConversionFun(char ch) {
  unsigned char c2 = ch;

  if(c2 <= 0x1f) c2 += 0x60;
  else if(c2 >= 0x60 && c2 <= 0x7f) c2 -= 0x60;
  else if(c2 == 0x00 || c2 == 0x40) c2 ^= 0x40;

  return c2;
}

void r8b_printStringScreen(R8B_Tiled_Screen *scr, R8B_CharConversionFun *fun, const char *txt) {
  const char *p;
 
  for(p = txt; *p != 0; ++p) {
    r8b_putCharScreen(scr, fun(*p));
  }
}

void r8b_printStringScreenLn(R8B_Tiled_Screen *scr, R8B_CharConversionFun *fun, const char *txt) {
  r8b_printStringScreen(scr, fun, txt);
  scr->crsr_x = 0;
  r8b_moveScreenCursor(scr, 0, 1);  
}
